package com.hyt.it.ogt.kq.service.gov.arrange;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.enums.ArrangeRuleEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ArrangeParamCodeEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.StringCodeEnum;
import com.hyt.it.ogt.kq.service.gov.mapper.ArrangeMapper;
import com.hyt.it.ogt.kq.service.gov.mapper.TimeRoomCandidateMapper;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.param.ArrangeParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.examiner.TimeRoomParamVO;
import com.hyt.it.ogt.kq.service.gov.schedule.model.Candidate;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ExamInfo;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ScheduleConstrain;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ScheduleStrategyEnum;
import com.hyt.it.ogt.kq.service.gov.service.*;
import com.hyt.loginfo.model.Organization;
import com.hyt.loginfo.service.IUaDeptService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 编排抽象类，提取部分通用方法
 */
public abstract class ArrangeAbstractService {
    @Resource
    protected ITimeRoomCandidateService iTimeRoomCandidateService;
    @Resource
    protected ICandidateService iCandidateService;
    @Resource
    protected ITimeSubjectService iTimeSubjectService;
    @Resource
    protected ITaskRoomService iTaskRoomService;
    @Resource
    protected ITimeService iTimeService;
    @Resource
    protected ITimeRoomService iTimeRoomService;
    @Resource
    protected ITimeRoomParamService iTimeRoomParamService;
    @Resource
    protected ITaskPlaceService iTaskPlaceService;
    @Resource
    protected ICandidateSubjectService iCandidateSubjectService;
    @Resource
    protected RedisTemplate redisTemplate;
    @Resource
    protected AdmissionService admissionService;
    @Resource
    protected ArrangeMapper arrangeMapper;
    @Resource
    protected IUaDeptService iUaDeptService;
    @Resource
    protected TimeRoomCandidateMapper timeRoomCandidateMapper;
    @Resource
    protected ITimeRoomExcludeService iTimeRoomExcludeService;
    @Resource
    protected ITimeRoomPasswordService iTimeRoomPasswordService;
    @Resource
    protected IExamMonitorService iExamMonitorService;
    @Resource
    protected ITaskService iTaskService;
    @Resource
    protected ICustomArrangeService iCustomArrangeService;
    @Resource
    protected ITaskCandidateAreaService iTaskCandidateAreaService;
    @Resource
    protected ITaskAreaService iTaskAreaService;

    /**
     * 编排接口
     */
    public abstract Object arrange(ArrangeParam param);

    /**
     * 初始化场次表
     * @param param
     * @param timeRoomCandidates
     * @return
     */
    protected void initTimeRoom(ArrangeParam param, List<TimeRoomCandidate> timeRoomCandidates, Map<String, String> timeRoomMap) {
        String taskId = param.getTaskId();
        // 是否线下模式
        boolean isOffLine = "2".equals(iTaskService.getById(param.getTaskId()).getExamType());
        // 按批次考场分组统计考生编排数量
        Map<String, Long> timeRoomArrangeNumMap = timeRoomCandidates.stream().collect(Collectors.groupingBy(s -> s.getTimeId() + "_" + s.getRoomId(), Collectors.counting()));
        // 定义场次
        List<TimeRoom> timeRoomList = new ArrayList<>();
        // 先更新场次表所有数据为假删状态
        iTimeRoomService.lambdaUpdate().set(BaseEntity::getDelFlag, true).eq(TimeRoom::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).update();
        // 查询所有考场
        List<TaskRoom> taskRoomList = iTaskRoomService.lambdaQuery().eq(TaskRoom::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).list();
        // 查询所有批次
        List<Time> timeList = iTimeService.lambdaQuery().eq(Time::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).list();
        // 查询每个批次忽略的考场列表
        Map<String, String> ignoreTimeRoomMap = iTimeRoomExcludeService.lambdaQuery().eq(TimeRoomExclude::getTaskId, taskId)
                .eq(BaseEntity::getDelFlag, false).list()
                .stream().collect(Collectors.toMap((e -> e.getTimeId() + "_" + e.getRoomId()), TimeRoomExclude::getRoomId));
        // 开始遍历批次
        int code = 1;
        for (Time time : timeList) {
            // 按照每个批次都考所有考场遍历
            for(TaskRoom item : taskRoomList){
                // 过滤这个批次需要忽略的考场
                if (!ignoreTimeRoomMap.containsKey(time.getId() + "_" + item.getId())) {
                    TimeRoom timeRoomVO = TimeRoom.builder().roomId(item.getId()).timeId(time.getId())
                            .taskId(taskId).maxCount(item.getSeatNum()).planCount(item.getSeatNum()).build();
                    // 虚拟一个id，后面用得到
                    Long c = timeRoomArrangeNumMap.get(timeRoomVO.getTimeId() + "_" + timeRoomVO.getRoomId());
                    String timeRoomId = timeRoomMap.get(timeRoomVO.getTimeId() + "_" + timeRoomVO.getRoomId());
                    timeRoomVO.setActualCount(c == null ? 0 : c.intValue());
                    timeRoomVO.setId(StrUtil.isNotBlank(timeRoomId) ? timeRoomId : UUIDUtils.newSortUUID());
                    timeRoomVO.setCode(String.valueOf(code));
                    // 得到实际可编排的考场（不一定所有考场都编排了考生）
                    timeRoomList.add(timeRoomVO);
                    code++;
                }
            }
        }
        // 保存场次表
        iTimeRoomService.saveBatch(timeRoomList, 1000);
        if (isOffLine) {
            // 线下模式还需要生成每个场次对应的密码和监控表数据
            for (Time time : timeList) {
                //初始化场次密码
                iTimeRoomPasswordService.initTimeRoomPassword(taskId, time.getId());
                //初始化监控表
                iExamMonitorService.initExamMonitor(taskId, time.getId());
            }
        }
    }

    /**
     * 构造编排参数对象
     * @param param
     * @return
     */
    protected ScheduleConstrain buildScheduleConstrain(ArrangeParam param) {
        // 编排规则
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        String arrangeRule = rule.get().getValue();
        // 考场单多科目
        Optional<TimeRoomParamVO> isSingleSubject = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_ROOM_SUBJECT.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        // 是否固定考场（保持座位号）
        Optional<TimeRoomParamVO> isMainTainPosition = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_SAME_ROOM.getCode()
                        .equalsIgnoreCase(item.getCode()))
                .findFirst();
        // 考场最大编排考生数（仅虚拟考场编排使用）
        Optional<TimeRoomParamVO> roomMaxCandidateCount = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ROOM_MAX_CANDIDATE_COUNT.getCode()
                        .equalsIgnoreCase(item.getCode()))
                .findFirst();

        // 编排规则
        Optional<TimeRoomParamVO> config = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_CONFIG.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        String arrangeConfig = config.get().getValue();
        // 构造参数对象
        ScheduleConstrain scheduleConstrain = ScheduleConstrain.builder()
                .isSingleSubject("0".equals(isSingleSubject.get().getValue()) ? true : false)
                .isMainTainPosition("1".equals(isMainTainPosition.get().getValue()) ? true : false)
                .arrangeConfig(arrangeConfig)
                .scheduleTaskId(param.getTaskId())
                .strategy(ScheduleStrategyEnum.DEFAULT)
                .virCapacity(Integer.valueOf(roomMaxCandidateCount.get().getValue()))
                .build();

        Integer level = null;
        if (String.valueOf(ArrangeRuleEnum.SELF.getCode()).equals(arrangeRule)) {
            // 自定义层级
            Optional<TimeRoomParamVO> obj = param.getTimeRoomParamVOList().stream()
                    .filter(item -> StringCodeEnum.ARRANGE_SELF_LEVEL.getCode().equalsIgnoreCase(item.getCode()))
                    .findFirst();
            // 第几级
            level = Integer.valueOf(obj.get().getValue());
            // 按照约定设置参数（与编排算法的字段名称保持一致），有多少级，设置多少个参数
            scheduleConstrain.setGroupAttrs(this.getGroupAttr(1, level));
        } else if (String.valueOf(ArrangeRuleEnum.DEPT.getCode()).equals(arrangeRule)) {
            // 机构层级
            Optional<TimeRoomParamVO> obj = param.getTimeRoomParamVOList().stream()
                    .filter(item -> StringCodeEnum.ARRANGE_OFFICE_LEVEL.getCode().equalsIgnoreCase(item.getCode()))
                    .findFirst();
            // 第几级
            level = Integer.valueOf(obj.get().getValue());
            // 按照约定设置参数（与编排算法的字段名称保持一致），有多少级，设置多少个参数
            scheduleConstrain.setGroupAttrs(this.getGroupAttr(0, level));
        } else {
            // 按照约定，随机编排，默认没有级别
            scheduleConstrain.setGroupAttrs(new ArrayList<>());
        }
        return scheduleConstrain;
    }

    protected List<String> getGroupAttr(int type, int size) {
        List<String> g = new ArrayList<>();
        if (1 == type) {
            // 自定义
            for (int i = 0; i < size; i++) {
                g.add(groupAttr_self.get(i));
            }
        } else {
            // 按机构
            for (int i = 0; i < size; i++) {
                g.add(groupAttr_office.get(i));
            }
        }
        return g;
    }

    /**
     * 筛选每个批次下，符合条件的考生，只有考生符合要求第几级才算，向下兼容
     * @param param 前端请求参数
     * @param level 按第几级编排
     * @param officeMap 当前用户所在机构所有机构层级
     * @return
     */
    protected List<ExamInfo> getExamInfoList(ArrangeParam param, int level, Map<String, Organization> officeMap) {
        String taskId = param.getTaskId();
        // 编排规则
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        int arrangeRule = Integer.valueOf(rule.get().getValue());
        // 批次考生数据
        List<ExamInfo> examInfos = new ArrayList<>();
        // 查询所有批次和科目关系
        List<TimeSubject> timeList = iTimeSubjectService.lambdaQuery().eq(TimeSubject::getTaskId, taskId)
                .eq(BaseEntity::getDelFlag, false).list();
        // 按照批次分组，得到每个批次绑定所有的科目
        Map<String, List<TimeSubject>> timeMap = timeList.stream()
                .collect(Collectors.groupingBy(TimeSubject::getTimeId));

        // 编排任务下所有批次
        timeMap.forEach((k, v) -> {
            // 定义批次列表（去重），无关批次
            Set<String> subjects = new TreeSet<>();
            // 定义考生信息列表
            List<Candidate> candidates = new ArrayList<>();
            // 遍历一个批次下所有科目
            v.stream().forEach(item -> {
                // 科目
                subjects.add(item.getSubjectId());
                // 查询一个批次科目下所有的考生
                List<Candidate> list = arrangeMapper.getArrangeCandidateList(taskId, item.getSubjectId(), arrangeRule);
                // 遍历考生
                list = list.stream().filter(c -> {
                    if (level == 0) { // 随机编排
                        c.setProvince(null);
                        c.setCity(null);
                        c.setDistrict(null);
                        c.setLevel(null);
                        return true;
                    } else { // 按机构或自定义层级编排
                       // 定义是否过滤该考生
                        boolean flag = false;
                        // 按机构层级编排
                        if (ArrangeRuleEnum.DEPT.getCode() == arrangeRule) {
                            // 对考生对象进行机构层级的赋值
                            this.setCandidateLevelValue(c, officeMap);
                            if (level == 1 && StringUtils.isNotBlank(c.getLevel())) { // 1级编排
                                flag = true;
                            } else if (level == 2 && StringUtils.isNotBlank(c.getLevelTwo())) { // 2级编排
                                flag = true;
                            } else if (level == 3 && StringUtils.isNotBlank(c.getLevelThree())) { // 3级编排
                                flag = true;
                            } else if (level == 4 && StringUtils.isNotBlank(c.getLevelFour())) { // 4级编排
                                flag = true;
                            }
                        } else if (ArrangeRuleEnum.SELF.getCode() == arrangeRule) { // 自定义层级
                            if (level == 1 && StringUtils.isNotBlank(c.getProvince())) { // 1级编排
                                flag = true;
                            } else if (level == 2 && StringUtils.isNotBlank(c.getCity())) { // 2级编排
                                flag = true;
                            } else if (level == 3 && StringUtils.isNotBlank(c.getDistrict())) { // 3级编排
                                flag = true;
                            }
                        }
                        return flag;
                    }
                }).collect(Collectors.toList());
                // 得到符合条件的考生
                candidates.addAll(list);
            });
            if (candidates.size() > 0) {
                ExamInfo examInfo = ExamInfo.builder().batchId(k).subjects(new ArrayList<>(subjects)).candidates(candidates).build();
                examInfos.add(examInfo);
            }
        });
        return examInfos;
    }

    /**
     * 设置考生对象的机构层级值
     * @param c
     * @param officeMap
     */
    protected void setCandidateLevelValue(Candidate c, Map<String, Organization> officeMap) {
        // 根据考生绑定的机构id，获取机构信息
        Organization dept = officeMap.get(c.getLevel());
        if (dept == null) {
            dept = iUaDeptService.getDeptById(c.getLevel());
            officeMap.put(c.getLevel(), dept);
        }
        if(dept == null) {
            return;
        }
        // 拆分出机构的层级（父子节点关系）
        String[] split = dept.getAncestors().split(",");
        for (int i = 0; i < split.length; i++) {
            String s = split[i];
            if (i == 0) {
                c.setLevel(s);
            } else if (i == 1) {
                c.setLevelTwo(s);
            } else if (i == 2) {
                c.setLevelThree(s);
            } else if (i == 3) {
                c.setLevelFour(s);
            }
        }
    }

    /**
     * 公共校验规则（仅编排方式为虚拟考场编排和实体考场编排使用）
     * @param param
     */
    protected void commonCheck(ArrangeParam param) {
        // 编排规则
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        String arrangeRule = rule.get().getValue();
        // 编排方式
        Optional<TimeRoomParamVO> type = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_TYPE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        String arrangeType = type.get().getValue();

        if (String.valueOf(ArrangeRuleEnum.SELF.getCode()).equals(arrangeRule)) { // 自定义层级划分
            // 统计任务下所有考生是否存在没有自定义层级的考生，只要有一个考生不存在省市县字段数据（按照约定要么都没有要么3级都有数据），则不给编排
            Integer count = iCandidateService.lambdaQuery().eq(com.hyt.it.ogt.kq.service.model.entity.Candidate::getTaskId, param.getTaskId())
                    .isNotNull(com.hyt.it.ogt.kq.service.model.entity.Candidate::getProvince).eq(BaseEntity::getDelFlag, false).count();
            if (count == 0) {
                throw new KqException(ResponseCode.ARRANGE_CANDIDATE_SELF_LEVEL_NULL.getCode(), ResponseCode.ARRANGE_CANDIDATE_SELF_LEVEL_NULL.getMsg());
            }
            if ("1".equalsIgnoreCase(arrangeType)) { // 实体考场编排
                // 还需要校验是否存在考点考场数据
                count = iTaskPlaceService.lambdaQuery().eq(TaskPlace::getTaskId, param.getTaskId())
                        .isNotNull(TaskPlace::getProvince).eq(BaseEntity::getDelFlag, false).count();
                if (count == 0) {
                    throw new KqException(ResponseCode.ARRANGE_ROOM_SELF_LEVEL_NULL.getCode(), ResponseCode.ARRANGE_ROOM_SELF_LEVEL_NULL.getMsg());
                }
            }
        } else if (String.valueOf(ArrangeRuleEnum.DEPT.getCode()).equals(arrangeRule)) { // 按机构编排
            // 统计任务下所有考生是否存在没有绑定机构的考生，只要有一个考生机构字段没有值，则不给编排
            Integer count = iCandidateService.lambdaQuery().eq(com.hyt.it.ogt.kq.service.model.entity.Candidate::getTaskId, param.getTaskId())
                    .isNotNull(com.hyt.it.ogt.kq.service.model.entity.Candidate::getDeptId).eq(BaseEntity::getDelFlag, false).count();
            if (count == 0) {
                throw new KqException(ResponseCode.ARRANGE_CANDIDATE_OFFICE_LEVEL_NULL.getCode(), ResponseCode.ARRANGE_CANDIDATE_OFFICE_LEVEL_NULL.getMsg());
            }
            if ("1".equalsIgnoreCase(arrangeType)) { // 实体考场编排
                // 还需要校验是否存在考点考场数据
                count = iTaskPlaceService.lambdaQuery().eq(TaskPlace::getTaskId, param.getTaskId())
                        .isNotNull(TaskPlace::getDeptId).eq(BaseEntity::getDelFlag, false).count();
                if (count == 0) {
                    throw new KqException(ResponseCode.ARRANGE_ROOM_OFFICE_LEVEL_NULL.getCode(), ResponseCode.ARRANGE_ROOM_OFFICE_LEVEL_NULL.getMsg());
                }
            }
        }
        // 校验批次是否绑定科目（自定义编排不需要绑定科目）
        Integer count = iTimeSubjectService.lambdaQuery().eq(TimeSubject::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false).count();
        if (count == 0) {
            throw new KqException(ResponseCode.ARRANGE_TIME_NOT_BING_SUBJECT.getCode(), ResponseCode.ARRANGE_TIME_NOT_BING_SUBJECT.getMsg());
        }
        // 考生报考多科目，多个科目不能在同一批次
        Integer c = arrangeMapper.checkCandidateSubjectTime(param.getTaskId());
        if(c != null && c > 0) {
            throw new KqException(ResponseCode.ARRANGE_SAME_SUBJECT_AND_TIME.getCode(), ResponseCode.ARRANGE_SAME_SUBJECT_AND_TIME.getMsg());
        }
    }

    /**
     * 获取考生自定义准考证号
     * @param taskId
     * @return
     */
    public Map<String, String> getCustomArrange(Integer admissionType, String taskId) {
        Map<String, String> map = new HashMap<>();
        if (ArrangeParamCodeEnum.ADMISSION_TYPE_CUSTOM.getCode().equals(admissionType)) {
            // 获取自定义编排数据，该表数量和考生表不一定一致
            List<CustomArrange> customArrangeList = iCustomArrangeService.listByTaskId(taskId);
            // 筛选出带有自定义编排信息的数据
            List<CustomArrange> hasArrangeList = customArrangeList.stream()
                    .filter(customArrange -> StringUtils.isBlank(customArrange.getAdmissionNumber()))
                    .collect(Collectors.toList());
            // 校验是否存在自定义准考证号
            if (CollectionUtils.isNotEmpty(hasArrangeList) || CollectionUtils.isEmpty(customArrangeList)) {
                // 存在考生没有自定义准考证号
                throw new KqException(ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getCode(), ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getMsg());
            }
            // 考生总数量
            Integer count = iCandidateService.lambdaQuery().eq(com.hyt.it.ogt.kq.service.model.entity.Candidate::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).count();
            if (customArrangeList.size() != count) {
                // 存在考生没有自定义准考证号
                throw new KqException(ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getCode(), ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getMsg());
            }
            // 考生ID-准考证号  关系映射
            map = customArrangeList.stream().collect(Collectors.toMap(CustomArrange::getCandidateId, CustomArrange::getAdmissionNumber));
        }
        return map;
    }

    // 机构层级
    private static List<String> groupAttr_office = Arrays.asList("level", "levelTwo", "levelThree", "levelFour");
    // 自定义层级
    private static List<String> groupAttr_self = Arrays.asList("province", "city", "district");
    // 默认字符
    public static final String DEFAULT = "default";
}
